package cn.xuqiudong.console.config.redis;

import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

/**
 * 配置redisTemplateCustomize 和 spring缓存管理器
 *
 * @author VIC.xu
 *
 */
@Configuration
@ConditionalOnExpression("${system.enabled:true}")
public class RedisConfig {

    public static final String CUSTOMIZE_REDIS_TEMPLATE = "redisTemplateCustomize";

    private static final Logger LOGGER = LoggerFactory.getLogger(RedisConfig.class);

    /**
     * 配置订制化的缓存超时时间， 单位小时 例如：custome.cache.map.cacheName1=2 表示cacheName1
     * 的缓存时间配置为2小时
     */
    @Value("#{${custome.cache.map:{}}}")
    public Map<String, Integer> customerCacheNameMap;

    /**
     * 使用Lettuce 连接池的方式
     *
     * @param connectionFactory RedisConnectionFactory
     * @return RedisTemplate
     */
    @Bean(CUSTOMIZE_REDIS_TEMPLATE)
    @ConditionalOnClass(RedisConnectionFactory.class)
    public RedisTemplate<String, Object> redisTemplateCustomize(RedisConnectionFactory connectionFactory) {
        LOGGER.info("redisTemplateCustomize Initialize");
        // 配置redisTemplate
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        // key序列化
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        // value序列化
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setHashKeySerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }


    private Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer(ObjectMapper objectMapper) {
        Jackson2JsonRedisSerializer<Object> jsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        objectMapper.setVisibility(PropertyAccessor.ALL, Visibility.ANY);
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        jsonRedisSerializer.setObjectMapper(objectMapper);
        return jsonRedisSerializer;
    }

    /**
     *
     * 缓存配置管理器
     */
    @Bean
    @ConditionalOnClass(RedisConnectionFactory.class)
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        LOGGER.info("cacheManager initialized");
        // 以锁写入的方式创建RedisCacheWriter对象
        RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(factory);
        // 创建默认缓存配置对象
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().
                // 失效时间2 天
                        entryTtl(Duration.ofDays(2))
                .serializeKeysWith(
                        RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(jackson2JsonRedisSerializer(new ObjectMapper())));
        // 不缓存null
        // .disableCachingNullValues();

        // 订制化的缓存设置
        Map<String, RedisCacheConfiguration> expires = expires(config);

        // RedisCacheManager cacheManager = new RedisCacheManager(writer,
        // config);
        RedisCacheManager cacheManager = RedisCacheManager.RedisCacheManagerBuilder.fromCacheWriter(writer)
                .cacheDefaults(config)
                // 开启 put/evict 操作跟随spring事物
                .transactionAware().initialCacheNames(expires.keySet()).withInitialCacheConfigurations(expires).build();
        return cacheManager;
    }

    /**
     * redisService
     */
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnClass(RedisConnectionFactory.class)
    public RedisService redisService(@Qualifier(CUSTOMIZE_REDIS_TEMPLATE) RedisTemplate<String, Object> redisTemplate) {
        return new RedisService(redisTemplate);
    }

    /**
     * 订制化配置不同cacheName的失效时间
     *
     * @return
     */
    public Map<String, RedisCacheConfiguration> expires(RedisCacheConfiguration config) {
        Map<String, RedisCacheConfiguration> expires = new HashMap<>(8);
        if (customerCacheNameMap != null) {
            customerCacheNameMap.forEach((k, v) -> {
                expires.put(k, config.entryTtl(Duration.ofHours(v)));
            });
        }
        return expires;
    }

}
